<?php

namespace App\Services\Assemblers\Admin;

use App\Http\Requests\Admin\Panx\Dashboard\DashBoardDeviceAccountRequest;
use App\Models\Customer\Customer;
use App\Models\Customer\CustomerRelateHmSkin;
use App\Models\Customer\CustomerRelateWaxSkin;
use App\Models\Customer\DeviceAccount;
use App\Models\Customer\HmSkinSuggest;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\Device;
use App\Models\Panx\Product;
use App\Models\Panx\Record;
use App\Models\Panx\Sex;
use App\Models\Store\HmSkinRecord;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\WaxSkinRecord;
use App\Services\OpenWechat\WechatUserRepository;
use App\Services\Presenters\HmSkinData;
use App\Services\Presenters\WaxSkinData;
use App\Services\Utils\Guard;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

/**
 * panx dashboard 面板数据服务类
 * Author: lyun
 * Date: 2018/10/22
 * Created by Panxsoft.
 */


class PanxDashboardService
{
    public static $female_sex = 0;
    public static $male_sex = 0;
    public static $unknown_sex = 0;

    public static $age_16_19;
    public static $age_20_29;
    public static $age_30_39;
    public static $age_40_49;
    public static $age_50_59;
    public static $age_60;

    public static $province = array();

    protected $debug_customer_ids = array();

    /**
     * PanxDashboardService constructor.
     */
    public function __construct()
    {
        $this->debug_customer_ids = (new Customer)->where('is_debug', Customer::IS_DEBUG)
            ->pluck('id');
    }

    /**
     * @return int
     */
    public function assembleUserProfiles()
    {
        /** @var Collection $wechat_user */
        $wechat_user = (new WechatUser)
            ->has('skinRecords')
            ->select(['id', 'sex', 'born_at', 'province'])
            ->get();

        $wechat_user->each(function($total_count){
            $this->groupBySex($total_count->sex);
            $this->groupByAge($total_count->born_at);
            $this->groupByProvince($total_count->province);
        });

        foreach(self::$province as $province => $count)
        {
            $province_group[] = ['province' => $province, 'count' => $count];
        }

        $sex_group = [
            ['sex' => 0, 'count' => self::$unknown_sex],
            ['sex' => 1, 'count' => self::$male_sex],
            ['sex' => 2, 'count' => self::$female_sex]
        ];

        $born_range = [
            ['range' => '16-19岁', 'count' => self::$age_16_19],
            ['range' => '20-29岁', 'count' => self::$age_20_29],
            ['range' => '30-39岁', 'count' => self::$age_30_39],
            ['range' => '40-49岁', 'count' => self::$age_40_49],
            ['range' => '50-59岁', 'count' => self::$age_50_59],
            ['range' => '60岁及以上', 'count' => self::$age_60],
        ];
        $total = $wechat_user->count();

        return compact('sex_group','province_group', 'born_range', 'total');
    }

    private function groupBySex($sex)
    {
        if ($sex === 2) {
            self::$female_sex++;
        } else if ($sex === 1) {
            self::$male_sex++;
        } else {
            self::$unknown_sex++;
        }
    }

    private function groupByAge(?Carbon $born_at)
    {
        if (!$born_at) return false;

        $age = $born_at->diffInYears();

        if ($age>=16 && $age<=19) {
            self::$age_16_19++;
        } else if ($age>=20 && $age<=29){
            self::$age_20_29++;
        } else if ($age>=30 && $age<=39) {
            self::$age_30_39++;
        } else if ($age>=40 && $age<=49) {
            self::$age_40_49++;
        } else if ($age>=50 && $age<=59) {
            self::$age_50_59++;
        } else if ($age>=60) {
            self::$age_60++;
        }
    }

    private function groupByProvince($province)
    {
        if (isset(self::$province[$province])) {
            self::$province[$province]++;
        } else {
            self::$province[$province] = 1;
        }
    }

    /**
     * @return array
     */
    public function getAssetTryOnCount()
    {
        $product_try_on_count = $this->groupTryOnCountByProductCategory();
        $total_count          = $product_try_on_count->sum('total');

        return [
            'product_try_on_count' => $product_try_on_count->map(function ($product_count) use($total_count){
                return [
                    'category_id'   => $product_count->category_id,
                    'category_name' => $product_count->category->name ?? trans('service.tip'),
                    'count'         => $product_count->total ?? 0,
                    'total'         => $total_count
                ];
            }),
        ];
    }

    /**
     * @param DashBoardDeviceAccountRequest $request
     * @return Record[]|Builder[]|Collection|\Illuminate\Support\Collection
     */
    public function getDeviceAccountTryOnCount(DashBoardDeviceAccountRequest $request)
    {
        $device_ids = explode(',', $request->device_id) ?? [$request->device_id];

        $device_account_ids = (new DeviceAccount)->whereIn('device_id', $device_ids)
            ->pluck('id');

        /** @var LengthAwarePaginator $records */
        $records = (new Record)->where('record_type_id', Record::TRY_ON)
            ->whereIn('recorderable_id', $device_account_ids)
            ->groupBy('recorderable_id')
            ->with('recorderable.store', 'recorderable.device', 'recorderable.customer')
            ->select(
                'recorderable_id',
                DB::raw('count(*) as count'),
                DB::raw('max(recorderable_type) as recorderable_type')
            )
            ->latest('count')
            ->paginate($request->per_page?:15);

        $records->getCollection()->transform(function(Record $records){
            $records->device_code = $records->recorderable->device->code ?? null;
            $records->device_name = $records->recorderable->device_name ?? null;
            $records->enabled = $records->recorderable->enabled ?? false;
            $records->store_name = $records->recorderable->store->name ?? null;
            $records->customer_name = $records->recorderable->customer->name ?? null;

            $records->addHidden('recorderable');

            return $records;
        });

        return $records;
    }

    /**
     * @return Collection|\Illuminate\Support\Collection
     */
    public function getDeviceTryOnCount()
    {
        $records = (new Record)->where('record_type_id', Record::TRY_ON)
            ->groupBy('recorderable_id')
            ->with('recorderable.store', 'recorderable.device')
            ->select(
                'recorderable_id',
                DB::raw('count(*) as count'),
                DB::raw('max(recorderable_type) as recorderable_type')
            )
            ->pluck('count', 'recorderable_id')
            ->all();

        /** @var Collection $devices */
        $devices = Device::filterDeviceCode()
            ->groupBy('code')
//            ->with('deviceAccounts')
            ->select('code', DB::raw('GROUP_CONCAT(id) AS device_id'))
            ->get();

        $devices = $devices->map(function(Device $device) use($records){
            $device->count = $this->getTryOnCount($device->device_id, $records);

            $device->addHidden('deviceAccounts');

            return $device;
        })
            ->sortByDesc('count')
            ->values();

        return $devices;
    }

    /**
     * @param Request $request
     * @return array
     */
    public function getCustomerProfiles(Request $request)
    {
        $customer_builder = (new Customer)->where('is_debug', '<>',Customer::IS_DEBUG)
            ->where('is_deleted', Customer::IS_NOT_DELETED)
            ->where('provider_type', Customer::PROVIDER_TYPE_NORMAL)
            ->with([
                'stores'                  => $this->withQuerySelect(['id', 'customer_id']),
                'deviceAccounts'          => $this->withQuerySelect(['id', 'customer_id']),
                'skinRecords'             => $this->withQuerySelect([(new SkinRecord)->getQualifiedKeyName()]),
                'products'                => $this->withQuerySelect(['id', 'try_on', 'customer_id']),
                // eager load two type of wechat users
                'noAuthorizerWechatUsers' => $this->withQuerySelect(['id', 'customer_id']),
                'storeWechatUserPivots',
            ])
            ->select(['id', 'name', 'authorizer_id'])
            ->latest();

        /** @var LengthAwarePaginator $customers */
        $customers = $customer_builder->paginate($request->per_page?:15);

        $customers->getCollection()->transform(function(Customer $customer){
            $customer->store_count = $customer->stores->count();
            $customer->device_account_count = $customer->deviceAccounts->count();
            $customer->skin_record_count = $customer->skinRecords->count();
            $customer->product_tryon_count = $customer->products->sum('try_on');
            $customer->wechat_user_count = $this->getCustomerWechatUserCount($customer);

            $customer->addHidden(
                'stores', 'deviceAccounts', 'products', 'skinRecords',
                'noAuthorizerWechatUsers', 'storeWechatUserPivots'
            );

            return $customer;
        });

        $customer_count = $customers->total();
        $device_count = (new DeviceAccount)->whereNotIn('customer_id', $this->debug_customer_ids)
            ->count();

        return compact('customer_count', 'device_count', 'customers');
    }

    /**
     * @return Product[]|Builder[]|Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
     */
    private function groupTryOnCountByProductCategory()
    {
        $try_on_count = (new Product)->whereNotIn('customer_id', $this->debug_customer_ids)
            ->groupBy(['category_id'])
            ->with('category')
            ->select(['category_id', DB::raw('sum(try_on) as total')])
            ->latest('total')
            ->get();

        return $try_on_count;
    }

    /**
     * @param Customer $customer
     * @return int
     */
    private function getCustomerWechatUserCount(Customer $customer)
    {
        if($customer->authorizer_id) {
            return $customer->storeWechatUserPivots->unique('wechat_user_id')->count();
        } else {
            return $customer->noAuthorizerWechatUsers->count();
        }
    }

    /**
     * 返回测肤占比的数据hm/wax
     *
     * @throws \Exception
     * @return array
     */
    public function skinStatusProportion(): array
    {
        $hm_proportion = $this->hmProportion();
        $wax_proportion = $this->waxItemCount();

        return compact('hm_proportion', 'wax_proportion');
    }

    /**
     * @return array
     * @throws \Exception
     */
    private function hmProportion(): array
    {
        $hm_skin_records_count = (new HmSkinRecord)
            ->where('wechat_user_id', '<>', 0)
            ->count();

        // most frequently
        $flex_most_appeared = $this->mostHmAppeared($hm_skin_records_count, HmSkinData::FLEX_LABEL_MAP);
        $oil_most_appeared = $this->mostHmAppeared($hm_skin_records_count, HmSkinData::OIL_LABEL_MAP);
        $water_most_appeared = $this->mostHmAppeared($hm_skin_records_count, HmSkinData::WATER_LABEL_MAP);

        $flex_proportion = $flex_most_appeared[1] / $hm_skin_records_count * 100;
        $oil_proportion = $oil_most_appeared[1] / $hm_skin_records_count * 100;
        $water_proportion = $water_most_appeared[1] / $hm_skin_records_count * 100;

//        $hm_suggests = HmSkinData::getHmSkinSuggest(CustomerRelateHmSkin::CUSTOMER_ID_DEFAULT);

        $hm_data = [
            [
                'skin_type'  => 'flex',
//                'title'      => HmSkinData::appLocaleReportMap()['flex'][$flex_most_appeared[0]]['title'],
                'title'      => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_FLEX, $flex_most_appeared[0], CustomerRelateHmSkin::CUSTOMER_ID_DEFAULT),
                'proportion' => $this->keepTwoDecimals($flex_proportion),
            ],
            [
                'skin_type'  => 'oil',
//                'title'      => HmSkinData::appLocaleReportMap()['oil'][$oil_most_appeared[0]]['title'],
                'title'      => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_OIL, $oil_most_appeared[0], CustomerRelateHmSkin::CUSTOMER_ID_DEFAULT),
                'proportion' => $this->keepTwoDecimals($oil_proportion),
            ],
            [
                'skin_type'  => 'water',
//                'title'      => HmSkinData::appLocaleReportMap()['water'][$water_most_appeared[0]]['title'],
                'title'      => HmSkinData::transScoreTitleByCustom(HmSkinData::HM_WATER, $water_most_appeared[0], CustomerRelateHmSkin::CUSTOMER_ID_DEFAULT),
                'proportion' => $this->keepTwoDecimals($water_proportion),
            ],
        ];

        return $hm_data;
    }
    
    /**
     * @return array
     */
    private function waxItemCount(): array
    {
        $level_sorts = array_keys( WaxSkinData::WAX_SKIN_DATA['zh'][1]);

        $wax_skin_records = (new WaxSkinRecord)
            ->select(
                'itemsid',
                'levelsort',
                DB::raw('COUNT(*) AS count')
            )
            ->whereIn('itemsid', WaxSkinRecord::ITEMSID_ALL)
            ->whereIn('levelsort',$level_sorts)
            ->groupBy('itemsid','levelsort')
            ->orderByDesc('count')
            ->get();

        $items = $wax_skin_records->groupBy('itemsid')
            ->map(function (Collection $records, $itemsid) {
                return [
                    'itemsid'     => $itemsid,
//                    'levelname'   => WaxSkinData::WAX_SKIN_DATA[trans('app.locale_language')][$itemsid][$records[0]['levelsort']]['levelname'],
                    'levelname'   => WaxSkinData::transScoreLevelNameByCustom($itemsid, $records[0]['levelsort'], CustomerRelateWaxSkin::CUSTOMER_ID_DEFAULT),
                    'count'       => $records[0]['count'],
                    'total_count' => $records->sum('count'),
                ];
            })
            ->values();

        return compact('items');
    }

    /**
     * @param $hm_skin_records_count
     * @param array $label_map
     * @return array
     * @throws \Exception
     */
    private function mostHmAppeared($hm_skin_records_count, array $label_map)
    {
        $max_count = 0;
        $remain_count = $hm_skin_records_count;
        $label_key_count_map = [];

        foreach ($label_map as $label_key)
        {
            if ($max_count < $remain_count) {
                $label = head(explode('_', $label_key));
                $count = (new HmSkinRecord)->where('wechat_user_id', '<>', 0)
                    ->where(HmSkinData::skinRecordWhereRangeByLabelKey($label, $label_key))
                    ->count();

                $remain_count -= $count;
                $label_key_count_map[$label_key] = $count;

                $max_count = max($label_key_count_map);
            }
        }

        $max_label_key = array_search($max_count, $label_key_count_map);
        $max_label_key = last(explode('_', $max_label_key));

        return [$max_label_key, $max_count];
    }

    /**
     * @param $num
     * @return string
     */
    private function keepTwoDecimals($num)
    {
        return sprintf("%.2f", $num);
    }

    private function getTryOnCount($device_id, array $records)
    {
        $device_ids = explode(',', $device_id) ?? [$device_id];
        $device_accounts = (new DeviceAccount)->whereIn('device_id', $device_ids)
            ->select(['id'])
            ->get();

        $device_accounts->transform(function($device_account) use($records){;
            $device_account->count = $records[$device_account->id] ?? 0;

            return $device_account;
        });

        return $device_accounts->sum('count');
    }

    /**
     * @param array $columns
     *
     * @return \Closure
     */
    private function withQuerySelect(array $columns = ['*']): \Closure
    {
        return function (Relation $query) use ($columns) {
            $query->select($columns);
        };
    }
}